﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Xml;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Specialized;

namespace Thingtory
{
    /// <summary>
    /// Summary description for InvTools
    /// </summary>
    [WebService(Namespace = "http://thingtory.org/", Description = "Thingtory (Inventory of Things) API Web-Service. Copyright (c) 2014 by Roger Zander")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    [System.Web.Script.Services.ScriptService]
    public class ThingtoryAPI : System.Web.Services.WebService
    {

        [WebMethod(Description="Create or update an existing Asset")]
        public string CreateNewAsset(string AccessKey, string HardwareIdentifier, string DisplayName = "")
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();

            try
            {
                //Validate AccessKey
                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;

                //Check if HWID already exists
                Asset oAsset = oDB.Asset.FirstOrDefault(t => t.CustomerID == customerID & t.HWID == HardwareIdentifier);
                if (oAsset == null)
                {
                    oAsset = new Asset() { CustomerID = customerID, HWID = HardwareIdentifier, AssetID = Guid.NewGuid(), TimeStamp = DateTime.Now.ToUniversalTime(), DisplayName = DisplayName };
                    oDB.Asset.Add(oAsset);
                    oDB.SaveChanges();

                    return oAsset.AssetID.ToString();
                }
                else
                {
                    if (!string.IsNullOrEmpty(DisplayName))
                    {
                        oAsset.DisplayName = DisplayName;

                        oDB.SaveChanges();
                    }

                }
            }
            catch { }
            return "";
        }

        [WebMethod(Description = "Upload Inventory data (full or delta)")]
        public bool UploadInventory(string AccessKey, string HardwareIdentifier, string InventoryXML)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            bool deltaInv = false;
            XmlDocument xdoc = new XmlDocument();

            try
            {
                if (AccessKey.ToUpper() == "DEMO")
                    return true;

                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;
                Asset oAsset = oDB.Asset.FirstOrDefault(t => t.CustomerID == customerID & t.HWID == HardwareIdentifier);

                if (oAsset == null)
                {
                    string AssetID = CreateNewAsset(AccessKey, HardwareIdentifier);
                    oAsset = oDB.Asset.First(t => t.CustomerID == customerID & t.HWID == HardwareIdentifier);
                }

                xdoc.LoadXml(InventoryXML);



                //check if Zipped
                if (xdoc.SelectSingleNode("GZIP") != null)
                {
                    InventoryXML = _decompressGZIP(xdoc.SelectSingleNode("GZIP").InnerText);
                    xdoc.LoadXml(InventoryXML);
                }

                //Check if delta or full inventory
                if (xdoc.SelectSingleNode("//Objects[@Status = 'Delta']") != null)
                    deltaInv = true;

                XmlDocument oDBCI = new XmlDocument();

                //Check if Inventory aleady exists...
                if (!string.IsNullOrEmpty(oAsset.CIData) & deltaInv)
                {
                    oDBCI.LoadXml(oAsset.CIData);

                    foreach (XmlNode xNode in xdoc.SelectNodes("//Objects/Object"))
                    {
                        try
                        {
                            XmlNode dbNode;
                            //Check if Object Node exists...
                            try
                            {
                                dbNode = oDBCI.SelectSingleNode("//Objects/Object[@GroupID='" + xNode.Attributes.GetNamedItem("GroupID").Value + "' and @Instance='" + xNode.Attributes.GetNamedItem("Instance").Value + "']");
                                if (dbNode == null)
                                {
                                    //Load full node
                                    dbNode = oDBCI.SelectSingleNode("//Objects").AppendChild(oDBCI.ImportNode(xNode, false));
                                }

                            }
                            catch
                            {
                                //Object Node is missing, import full node..
                                dbNode = oDBCI.SelectSingleNode("//Objects").AppendChild(oDBCI.ImportNode(xNode, false));
                            }

                            foreach (XmlNode propNode in xNode.SelectNodes("Property"))
                            {
                                try
                                {
                                    //Remove illegal XML characters
                                    propNode.InnerXml = propNode.InnerXml.Replace("&#x1F;", "");
                                    propNode.InnerXml = propNode.InnerXml.Replace("&#x1C;", "");
                                    propNode.InnerXml = propNode.InnerXml.Replace("&#x0;", "");

                                    if (propNode.Attributes.GetNamedItem("Action").Value.ToUpper() == "ADD" | propNode.Attributes.GetNamedItem("Action").Value.ToUpper() == "UPDATE")
                                    {
                                        //for the case that the property exists...
                                        XmlNode targetNode = dbNode.SelectSingleNode("Property[@Name='" + propNode.Attributes.GetNamedItem("Name").Value + "']");
                                        if (targetNode != null)
                                        {
                                            targetNode.InnerText = propNode.InnerText;
                                            continue;
                                        }
                                        else
                                        {
                                            XmlNode tempNode = propNode;
                                            tempNode.Attributes.RemoveNamedItem("Action");
                                            XmlNode oResult = dbNode.AppendChild(oDBCI.ImportNode(tempNode, true));
                                            continue;
                                        }
                                    }

                                    if (propNode.Attributes.GetNamedItem("Action").Value.ToUpper() == "REMOVE")
                                    {
                                        XmlNode targetNode = dbNode.SelectSingleNode("Property[@Name='" + propNode.Attributes.GetNamedItem("Name").Value + "']");
                                        targetNode.ParentNode.RemoveChild(targetNode);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    ex.Message.ToString();
                                }
                            }


                        }
                        catch { }
                    }

                    oAsset.CIData = oDBCI.InnerXml;
                    oAsset.TimeStamp = DateTime.Now.ToUniversalTime();
                    oDB.SaveChanges();

                    return true;
                }
                else
                {
                    //Remove "Action" Attributes
                    foreach (XmlNode xNode in xdoc.SelectNodes("//Objects/Object/Property"))
                    {
                        try
                        {
                            xNode.InnerXml = xNode.InnerXml.Replace("&#x1F;", "");
                            xNode.InnerXml = xNode.InnerXml.Replace("&#x1C;", "");
                            xNode.InnerXml = xNode.InnerXml.Replace("&#x0;", "");
                            xNode.Attributes.RemoveNamedItem("Action");
                        }
                        catch { }
                    }

                    oAsset.CIData = xdoc.OuterXml;
                    oAsset.TimeStamp = DateTime.Now.ToUniversalTime();
                    oDB.SaveChanges();

                    return true;
                }


            }
            catch (Exception ex)
            {
                ex.Message.ToString();
            }

            return false;
        }

        [WebMethod(Description="Get inventoryXML from an Asset")]
        public XmlDocument GetAssetInventory(string AccessKey, string HardwareIdentifier)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            try
            {
                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;

                Asset oAsset = oDB.Asset.FirstOrDefault(t => t.CustomerID == customerID & t.HWID == HardwareIdentifier);

                XmlDocument oDBCI = new XmlDocument();

                //Check if Inventory aleady exists...
                if (!string.IsNullOrEmpty(oAsset.CIData))
                {
                    oDBCI.LoadXml(oAsset.CIData);
                    return oDBCI;
                }
                else
                {
                    return null;
                }
            }
            catch { }

            return null;

        }

        public class partialAsset
        {
            public string HWID { get; set; }
            public string DisplayName { get; set; }
            public string LastUploadUTC { get; set; }
        }

        [WebMethod(Description = "Get a List of all DisplayNames and HardwareID's")]
        public List<partialAsset> GetAllAssets(string AccessKey)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();

            try
            {
                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;
                List<partialAsset> oResult = new List<partialAsset>();
                foreach (var oAsset in oDB.Asset.Where(t => t.CustomerID == customerID))
                {
                    partialAsset mAsset = new partialAsset() { DisplayName = oAsset.DisplayName, HWID = oAsset.HWID, LastUploadUTC = oAsset.TimeStamp.ToString() };
                    oResult.Add(mAsset);
                }

                return oResult;
            }
            catch { }
            return null;
        }

        [WebMethod(Description="Delete an existing Asset")]
        public bool DeleteAsset(string AccessKey, string HardwareIdentifier)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            try
            {
                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;
                Asset oAsset = oDB.Asset.FirstOrDefault(t => t.CustomerID == customerID & t.HWID == HardwareIdentifier);

                oDB.Asset.Remove(oAsset);

                oDB.SaveChanges();

                return true;
            }
            catch { }
            return false;
        }

        [WebMethod(Description = "Get a custom Inventory Schema (NOT in DEMO mode !)")]
        public XmlDocument GetInventorySchema(string AccessKey, string DeviceType)
        {
            AccessKey = "DEMO";
            
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            try
            {
                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;

                InventorySchema oInv = oDB.InventorySchema.First(t => t.CustomerID == customerID & t.DeviceType == DeviceType);
                XmlDocument xResult = new XmlDocument();
                xResult.LoadXml(oInv.CISchemaXML);

                return xResult;

            }
            catch { }
            return null;
        }

        //[WebMethod(Description = "Get the default Powershell Script for WindowsClients")]
        public string GetWindowsAgentPS(string AccessKey)
        {
            return Properties.Resources.WinAgent.Replace("$KEY$", AccessKey);
        }

        public string _decompressGZIP(string Base64GZIP)
        {
            try
            {
                byte[] data = System.Convert.FromBase64String(Base64GZIP);
                System.IO.MemoryStream MS = new System.IO.MemoryStream();
                MS.Write(data, 0, data.Length);
                MS.Seek(0, 0);

                System.IO.Compression.GZipStream CS = new System.IO.Compression.GZipStream(MS, System.IO.Compression.CompressionMode.Decompress);
                System.IO.StreamReader SR = new System.IO.StreamReader(CS);
                string sResult = SR.ReadToEnd();

                return sResult;
            }
            catch { }

            return "";

        }

        //[WebMethod(Description="Get Inventory-Data from a specific GroupId as DataSet")]
        public DataSet GetInventoryAll(string AccessKey, string GroupID)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();

            // First Create a New Connection
            System.Data.SqlClient.SqlConnection sqlConnection1 = new System.Data.SqlClient.SqlConnection();
            try
            {

                // Now Pass a Connection String To the Connection
                sqlConnection1.ConnectionString = oDB.Database.Connection.ConnectionString;

                // Now the Select statement you want to run
                string select = _getQuery(AccessKey, GroupID, "%");
                
                // Create an Adapter
                SqlDataAdapter da = new SqlDataAdapter(select, sqlConnection1);
                // Create a New DataSet
                DataSet ds = new DataSet();
                // Fill The DataSet With the Contents of the Stock Table
                da.Fill(ds, "inv");
                // Now Return ds which is a DataSet
                return (ds);
            }
            catch { }
            finally
            {
                sqlConnection1.Close();
            }

            return null;
        }

        public DataSet GetInventory_old(string AccessKey, string GroupID, string DisplayName )
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();

            // First Create a New Connection
            System.Data.SqlClient.SqlConnection sqlConnection1 = new System.Data.SqlClient.SqlConnection();
            try
            {

                // Now Pass a Connection String To the Connection
                sqlConnection1.ConnectionString = oDB.Database.Connection.ConnectionString;

                // Now the Select statement you want to run
                string select = _getQuery(AccessKey, GroupID, DisplayName);

                // Create an Adapter
                SqlDataAdapter da = new SqlDataAdapter(select, sqlConnection1);
                // Create a New DataSet
                DataSet ds = new DataSet();
                // Fill The DataSet With the Contents of the Stock Table
                da.Fill(ds, "inv");
                // Now Return ds which is a DataSet
                return (ds);
            }
            catch { }
            finally
            {
                sqlConnection1.Close();
            }

            return null;
        }

        [WebMethod(Description = "Get Inventory-Data from a specific GroupId as DataSet")]
        public DataSet GetInventory(string AccessKey, string GroupID, string DisplayName)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();

            // First Create a New Connection
            System.Data.SqlClient.SqlConnection sqlConnection1 = new System.Data.SqlClient.SqlConnection();
            try
            {
                sqlConnection1.ConnectionString = oDB.Database.Connection.ConnectionString;
                sqlConnection1.Open();
                string sQuery = string.Format(Properties.Resources.InvDataXMLQuery, AccessKey, GroupID, DisplayName);


                DataSet ds = new DataSet();
                DataTable dt = ds.Tables.Add("DATA");
                dt.Columns.Add("Assetname");

                //create columns
                foreach (string sColumn in _getListPropertiesNew(AccessKey, GroupID, DisplayName))
                {
                    dt.Columns.Add(sColumn);
                }


                SqlCommand oCMD = new SqlCommand(sQuery, sqlConnection1);
                SqlDataReader sdr = oCMD.ExecuteReader();
                while (sdr.Read())
                {
                    string Computername = sdr["DisplayName"] as string;

                    XmlDocument xdoc = new XmlDocument();
                    xdoc.LoadXml("<root>" + sdr["XMLData"] as string + "</root>");
                    foreach (XmlNode oNode in xdoc.SelectNodes("root/Object"))
                    {
                        try
                        {
                            DataRow dr = dt.NewRow();
                            dr["Assetname"] = Computername;
                            foreach (XmlNode pNode in oNode.SelectNodes("Property"))
                            {
                                if (!string.IsNullOrEmpty(pNode.InnerText))
                                {
                                    try
                                    {
                                        dr[pNode.Attributes["Name"].InnerText] = pNode.InnerText;
                                    }
                                    catch (Exception ex)
                                    {
                                        ex.Message.ToString();
                                    }
                                }
                            }

                            dt.Rows.Add(dr);
                        }
                        catch(Exception ex)
                        {
                            ex.Message.ToString();
                        }
                    }
                    
                    
                }

                return ds;

            }
            catch { }
            finally
            {
                sqlConnection1.Close();
            }

            return null;
        }

        [WebMethod(Description = "request an AccessKey for DEMO purpose (limited Nr. of Assets and unused Data will be deleted). Note: This is for DEMO purpose only !")]
        public string newDEMOAccesKey(string eMail)
        {
            try
            {
                if (eMail.Contains("@"))
                {
                    ThingtoryDBEntities oDB = new ThingtoryDBEntities();
                    string randomID = System.IO.Path.GetRandomFileName().Replace(".", "");
                    if (oDB.Customer.FirstOrDefault(t => t.AccessKey == randomID) == null)
                    {
                        Customer oCustomer = new Customer() { CustomerID = Guid.NewGuid(), DisplayName = eMail, TimeStamp = DateTime.UtcNow, AccessKey = randomID };
                        oDB.Customer.Add(oCustomer);
                        oDB.SaveChanges();

                        return randomID;
                    }
                }
            }
            catch { }
            return "System was unable to create an AccessKey. Please verify your input and try it again.";
        }

        [WebMethod(Description="Get Inventory Groups (Name, Description and GroupID) from InventorySchema")]
        public DataSet GetInventoryGroups(string AccessKey, string DeviceType)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            if (string.IsNullOrEmpty(AccessKey)) { AccessKey = "DEMO"; }
            if (string.IsNullOrEmpty(DeviceType)) { DeviceType = "WindowsClient"; }

            // First Create a New Connection
            System.Data.SqlClient.SqlConnection sqlConnection1 = new System.Data.SqlClient.SqlConnection();
            try
            {
                // Now Pass a Connection String To the Connection
                sqlConnection1.ConnectionString = oDB.Database.Connection.ConnectionString;

                // Now the Select statement you want to run
                string select = string.Format(Properties.Resources.GroupIDQuery, AccessKey, DeviceType);

                // Create an Adapter
                SqlDataAdapter da = new SqlDataAdapter(select, sqlConnection1);
                // Create a New DataSet
                DataSet ds = new DataSet();
                // Fill The DataSet With the Contents of the Stock Table
                da.Fill(ds, "grp");
                // Now Return ds which is a DataSet
                return (ds);
            }
            catch { }
            finally
            {
                sqlConnection1.Close();
            }

            return null;
        }

        [WebMethod(Description="Update an existing Inventory property with a new value")]
        public bool UpdateInventoryProperty(string AccessKey, string HWID, string GroupID, string InstanceID, string PropertyName, string PropertyValue)
        {
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            try
            {
                Guid customerID = oDB.Customer.First(t => t.AccessKey == AccessKey).CustomerID;
                Asset oAsset = oDB.Asset.FirstOrDefault(t => t.CustomerID == customerID & t.HWID == HWID);
                if (oAsset != null)
                {
                    XmlDocument xAsset = new XmlDocument();
                    xAsset.LoadXml(oAsset.CIData);
                    foreach (XmlNode xNode in xAsset.SelectNodes(string.Format("Objects/Object[@GroupID='{0}' and @Instance='{1}']/Property[@Name='{2}']",GroupID, InstanceID.Replace("&quot;", "\""), PropertyName)))
                    {
                        xNode.InnerText = PropertyValue;
                    }

                    oAsset.CIData = xAsset.OuterXml;
                    oDB.SaveChanges();

                    return true;
                }
            }
            catch { }

            return false;
        }

        public List<string> _getListProperties(string AccessKey, string GroupID, string DisplayName)
        {
            
            List<string> pResult = new List<string>();
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            System.Data.SqlClient.SqlConnection sqlConnection1 = new System.Data.SqlClient.SqlConnection();
            try
            {
                sqlConnection1.ConnectionString = oDB.Database.Connection.ConnectionString;
                sqlConnection1.Open();
                string sQuery = string.Format(Properties.Resources.PropertyListQuery, AccessKey, GroupID, DisplayName);

                SqlCommand oCMD = new SqlCommand(sQuery, sqlConnection1);
                SqlDataReader sdr = oCMD.ExecuteReader();

                while (sdr.Read())
                {
                    pResult.Add(sdr["Property"].ToString());
                }

                return pResult;

            }
            catch { }
            finally
            {
                sqlConnection1.Close();
            }

            return null;
        }

        public List<string> _getListPropertiesNew(string AccessKey, string GroupID, string DisplayName)
        {

            List<string> pResult = new List<string>();
            ThingtoryDBEntities oDB = new ThingtoryDBEntities();
            System.Data.SqlClient.SqlConnection sqlConnection1 = new System.Data.SqlClient.SqlConnection();
            try
            {
                sqlConnection1.ConnectionString = oDB.Database.Connection.ConnectionString;
                sqlConnection1.Open();
                string sQuery = string.Format(Properties.Resources.PropertyListQueryNew, AccessKey, GroupID, DisplayName);

                SqlCommand oCMD = new SqlCommand(sQuery, sqlConnection1);
                SqlDataReader sdr = oCMD.ExecuteReader();

                while (sdr.Read())
                {
                    pResult.Add(sdr["Property"].ToString());
                }

                return pResult;

            }
            catch { }
            finally
            {
                sqlConnection1.Close();
            }

            return null;
        }

        public string _getQuery(string AccessKey, string GroupID, string DisplayName)
        {
            try
            {
                List<string> pList = _getListProperties(AccessKey, GroupID, DisplayName);

                string sSub = "declare @cID uniqueidentifier; SELECT @cID = CustomerID FROM dbo.Customer WHERE AccessKey = '" + AccessKey + "';  \r" +
                              "SELECT DISTINCT DisplayName, \r";
                for (int i = 1; i <= pList.Count; i++)
                {
                    string sLine = sSub + "[" + pList[i-1] + "] = TypeNode.value('(Property)[" + i.ToString() + "]', 'varchar(max)')";
                    if (i != pList.Count)
                        sSub = sLine + ", \r";
                    else
                        sSub = sLine + "\r";
                }

                sSub = sSub + "FROM [ThingtoryDB].[dbo].[Asset] CROSS APPLY CIData.nodes('Objects/Object[@GroupID=\"" + GroupID + "\"]') AS XTbl(TypeNode) \r" +
                    "WHERE CustomerID = @cID and DisplayName like '" + DisplayName + "'";

                

                sSub.ToString();

                return sSub;

            }
            catch { }

            return null;
        }

    }
}
